package com.explorex.infra.puma.gateway.netty.ws;

import com.explorex.infra.puma.gateway.netty.common.AbstractBaseServer;
import com.explorex.infra.puma.gateway.netty.common.enums.EnumUserStatus;
import com.explorex.infra.puma.gateway.netty.common.security.EDCHUtil;
import com.explorex.infra.puma.gateway.netty.exeptions.MessageParseException;
import com.explorex.infra.puma.gateway.netty.ws.enums.MessageTypeEnum;
import com.explorex.infra.puma.gateway.netty.ws.message.Message;
import com.explorex.infra.puma.gateway.netty.ws.message.RespMessageBody;
import com.explorex.infra.puma.gateway.netty.ws.message.handler.EmptyMessageProcessor;
import com.explorex.infra.puma.gateway.netty.ws.message.handler.MessageCenter;
import com.explorex.infra.puma.gateway.netty.ws.utils.SocketUtil;

import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketCloseStatus;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;

import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Map;

/**
 * @author: JacceYang
 * @createTime: 2022/07/14 9:19 下午
 * @description: WebSocketEvent handler
 */
@Slf4j
public class DefaultWebSocketEvent implements WebSocketEvent {

    private static final String WEBSOCKET_EVENT = "WebSocket_Event";
    private static final String WEBSOCKET = "WebSocket";
    private static final String UP = "up";
    private final MessageCenter messageCenter = new MessageCenter();

    @Override
    public void onOpenEvent(AbstractBaseServer server, WebSocketSession session, HttpHeaders httpHeaders, Map<String, Object> parameter) throws NoSuchAlgorithmException {
        // Get Remote Client Ip
        String remoteIp = SocketUtil.getRemoteIp(httpHeaders, session.channel());
        session.setRemoteIp(remoteIp);
        session.setLocalIp(SocketUtil.getLocalIp(session.channel()));
        session.setIpLink(SocketUtil.getIpLink(session.channel()));
        session.setStatus(EnumUserStatus.ONLINE.getKey());
        if (httpHeaders.get("key") != null) {
            byte[] otherPk = Base64.getDecoder().decode(httpHeaders.get("key"));
            session.shareKey(EDCHUtil.generateSharKey(otherPk, session.getKeyPair()));
        }
    }

    @Override
    public void onCloseEvent(AbstractBaseServer server, WebSocketSession session) {
        log.info("onCloseEvent called, session:{}", session);
        if (session == null) {
            return;
        }
        EnumUserStatus userStatusBeforeChange = EnumUserStatus.getUserStatus(session.userStatus());
        session.setStatus(EnumUserStatus.OFFLINE.getKey());
    }

    @Override
    public void OnErrorEvent(WebSocketSession session) {
        log.info("OnErrorEvent called,session:{}", session);
    }

    @Override
    public void onMessageStringEvent(AbstractBaseServer server, WebSocketSession session, String msgStr) {
        long startTime = System.currentTimeMillis();
        if (StringUtil.isNullOrEmpty(msgStr)) {
            log.warn("invalid msg msg={}", msgStr);
            return;
        }
        Throwable exception = null;
        Integer bizId = 0;
        Message messageAck = new Message();
        try {
            // 1. 处理消息
            messageCenter.parseAndProcessMessage(session, msgStr, messageAck);
            // 2. 回复消息
            responseMessage(session, messageAck, startTime);
            //3. 后处理
            afterProcessMessage(session, messageAck);

            bizId = messageAck.getHeader().getBizId();
        } catch (MessageParseException e) {
            defaultErrorResponse(session, messageAck, "invalid message format", startTime);
            exception = e;
        } catch (Exception e) {
            log.error("onMessageStringEvent handler exception msgStr={}", msgStr, e);
            exception = e;
        } finally {
            session.refresh(false);
        }
    }

    private void afterProcessMessage(WebSocketSession session, Message message) {
        //对于关闭通道请求，在消息response之后再做关闭处理
        if (message != null && message.getHeader().getType() == MessageTypeEnum.CLOSE_SOCKET_ACK.getType()) {
            session.getChannel().writeAndFlush(new CloseWebSocketFrame(new WebSocketCloseStatus(4000, "client request close")));
            log.info("client request close,session={}", session);
        }
    }

    private void defaultErrorResponse(WebSocketSession session, Message messageAck, String message, long startTime) {

        short type = messageAck.getHeader().getType();
        messageAck.getHeader().setType((short) (type | 0x80));
        if (messageAck.getBody() == null) {
            messageAck.setBody(new EmptyMessageProcessor.EmptyMessage());
        }
        RespMessageBody body = (RespMessageBody) messageAck.getBody();
        body.setMsg(message);
        body.setCode(-1);
        responseMessage(session, messageAck, startTime);
    }

    private void responseMessage(WebSocketSession session, Message messageAck, long startTime) {
        if (session == null) {
            log.info("messageAck:{}", messageAck);
        }
        if (messageAck == null) {
//            log.info("messageAck error ,it is null,session={},messageAck={}", session.toString(), messageAck);
            return;
        }
        if (messageAck.getHeader().getType() == MessageTypeEnum.SILENCE.getType()) {
//            log.info("messageAck SILENCE ,session={},messageAck={}", session.toString(), messageAck);
            return;
        }
        session.sendMessage(messageAck);
    }

    @Override
    public void onMessageBinaryEvent(AbstractBaseServer server, WebSocketSession session, ByteBuf msg) {
        log.info("onMessageBinaryEvent called,msg={}", msg);
        // 对于消息文本的二进制直接做uft8 转化
        onMessageStringEvent(server, session, msg.toString(CharsetUtil.UTF_8));
        // 对于文件，image 等类型不应该走上面的逻辑转化为文本
    }
}
